#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
# new Env("杰士邦")
# cron: 20 9 * * *
✦ 小程序信息    ▸ 名称：杰士邦会员中心
▸ 类别：积分换实物
▸ 可换种类：成人用品类（200积分起换）
✦ 执行说明    ▸ 首次执行需手动进入小程序完成手机号绑定
▸ 每日自动执行签到任务+抽奖
✦ 环境变量    ▸ Applet_JSB_Wxid: 指定允许执行的账号(Wxid)，多个用英文逗号分隔
"""
import json
import time
import re
import os
import random
import urllib3
from datetime import datetime
from typing import Dict, Optional, Any

# 确保requests库已安装
try:
    import requests
    from requests.exceptions import RequestException
except ImportError:
    print("错误: 需要安装 requests 库，请执行: pip install requests")
    exit(1)

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 环境变量配置
LOCAL_PROXY = os.getenv("API_PROXY")  # 直接使用环境变量值，不添加"http://"
APP_ID = "wx5966681b4a895dee"

class JieshibangSignin:
    """杰士邦小程序签到"""
    def __init__(self):
        self.base_url = "https://api.vshop.hchiv.cn"
        self.app_id = APP_ID
        self.session = requests.Session()
        self.session.verify = False  # 禁用SSL验证
        self.headers = {
            "Host": "api.vshop.hchiv.cn",
            "xweb_xhr": "1",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF",
            "appenv": "test",
            "content-type": "application/json",
            "accept": "*/*",
            "referer": "https://servicewechat.com/wx5966681b4a895dee/47/page-frame.html",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9"
        }
        self.client_token = None

    def get_wx_list(self):
        """获取微信账户列表"""
        try:
            if not LOCAL_PROXY:
                print("⚠️ 未配置API_PROXY环境变量")
                return []
                
            base_url = LOCAL_PROXY if LOCAL_PROXY.startswith(('http://', 'https://')) else f"http://{LOCAL_PROXY}"
            url = f"{base_url.rstrip('/')}/getallwx"
            
            print(f"尝试连接协议服务: {url}")
            response = requests.get(url, timeout=15, verify=False)
            
            if response.status_code == 200:
                return response.json()
            print(f"获取微信列表失败: HTTP {response.status_code}")
        except RequestException as e:
            print(f"获取微信列表失败: {str(e)}")
        return []

    def get_wx_code(self, wxid):
        """获取小程序登录凭证"""
        try:
            if not LOCAL_PROXY:
                print("⚠️ 未配置API_PROXY环境变量")
                return None
                
            base_url = LOCAL_PROXY if LOCAL_PROXY.startswith(('http://', 'https://')) else f"http://{LOCAL_PROXY}"
            url = f"{base_url.rstrip('/')}/loginbyapp?Wxid={wxid}&appid={self.app_id}"
            
            print(f"获取登录Code: {url}")
            response = requests.get(url, timeout=15, verify=False)
            
            if response.status_code == 200:
                data = response.json()
                if data.get("code"):
                    return data["code"]
                print("响应中未找到code字段")
            print(f"获取wxCode失败: HTTP {response.status_code}")
        except RequestException as e:
            print(f"获取wxCode异常: {str(e)}")
        return None

    def get_timestamp(self) -> int:
        """获取13位时间戳"""
        return int(time.time() * 1000)

    def clear_session(self):
        """清理会话数据"""
        self.headers = {
            "Host": "api.vshop.hchiv.cn",
            "xweb_xhr": "1",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF",
            "appenv": "test",
            "content-type": "application/json",
            "accept": "*/*",
            "referer": "https://servicewechat.com/wx5966681b4a895dee/47/page-frame.html",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9"
        }
        if self.client_token:
            self.headers["authorization"] = f"Bearer {self.client_token}"
        print("会话数据已清理")

    def login(self, code: str) -> bool:
        """登录获取token"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/member/wechatlogin/authLoginApplet"
        params = {
            "sideType": "3",
            "mob": "",
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "wxInfo": code,
            "extend": json.dumps({
                "sourcePage": "/packageA/pages/integral-index/integral-index",
                "activityId": "",
                "sourceShopId": "",
                "guideNo": "",
                "way": "member",
                "linkType": "2001"
            }),
            "sessionIdForWxShop": ""
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("data", {}).get("code") == 200:
                client_token = data.get("data", {}).get("data", {}).get("clientToken")
                if client_token:
                    self.client_token = client_token
                    self.headers["authorization"] = f"Bearer {client_token}"
                    print("账号登录成功")
                    return True
            print("账号登录失败，请登录小程序手动签到一次授权手机号后再运行脚本")
        except RequestException as e:
            print(f"登录请求失败: {e}")
        return False

    def get_client_info(self) -> Optional[Dict[str, Any]]:
        """获取客户信息"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/member/tblogin/getClientInfo"
        params = {
            "sideType": "3",
            "shopNick": self.app_id
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("data", {}).get("code") == 200:
                client_data = data.get("data", {}).get("data", {})
                print(f"账号信息获取成功，当前积分: {client_data.get('residualIntegral', '未知')}")
                return client_data
            print(f"获取账号信息失败: {data.get('data', {}).get('message', '未知错误')}")
        except RequestException as e:
            print(f"获取账号信息请求失败: {e}")
        return None

    def get_signin_activity_id(self, mob: str) -> Optional[str]:
        """获取签到活动ID"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/common/common/get-customer-page"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "pageId": 102999,
            "pageType": 2
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success"):
                page_json_str = data.get("data", {}).get("result", {}).get("pageJson", "")
                if page_json_str:
                    try:
                        page_json = json.loads(page_json_str)
                        for module in page_json.get('moduleList', []):
                            if module.get('type') == 'iconNav':
                                if 'detail' in module and 'linkList' in module['detail']:
                                    for link in module['detail']['linkList']:
                                        if link.get('text') == '签到':
                                            return link.get('id')
                    except json.JSONDecodeError:
                        print("解析pageJson失败")
        except RequestException as e:
            print(f"获取签到活动ID失败: {e}")
        return None

    def signin(self, mob: str, activity_id: str) -> bool:
        """提交签到"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/api/play-default/sign/add-sign-new.do"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "activityId": activity_id
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success"):
                integral = data.get("data", {}).get("integral", 0)
                integral_alias = data.get("data", {}).get("integralAlias", "积分")
                print(f"签到成功，获得{integral}{integral_alias}")
                return True
            else:
                print(f"签到失败: {data.get('errorMessage', '未知错误')}")
        except RequestException as e:
            print(f"签到请求失败: {e}")
        return False

    def get_activity_list(self, mob: str) -> Optional[str]:
        """获取活动列表，查找包含"签到"的活动"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/activity/activity/activityList"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "pageNumber": 1,
            "pageSize": 20,
            "decoActStatus": ["1"]
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success") and data.get("data", {}).get("success"):
                data_list = data["data"]["data"].get("dataList", [])
                for activity in data_list:
                    name = activity.get("name", "")
                    if "签到" in name:
                        activity_id = str(activity.get("id", ""))
                        print(f"获取到{name}")
                        return activity_id
        except RequestException as e:
            print(f"获取活动列表失败: {e}")
        return None

    def get_sign_prize(self, mob: str, activity_id: str) -> Optional[str]:
        """获取签到活动奖励详情"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/activity/sign/getSignPrize"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "activityId": activity_id
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success"):
                response_list = data.get("data", {}).get("responseList", [])
                if response_list:
                    prize_name = response_list[0].get("prizeName", "未知奖品")
                    return prize_name
        except RequestException as e:
            print(f"获取签到奖励详情失败: {e}")
        return None

    def add_sign_new(self, mob: str, activity_id: str) -> Optional[Dict]:
        """提交签到活动"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/api/play-default/sign/add-sign-new.do"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "activityId": activity_id
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success"):
                result_data = data.get("data", {})
                integral = result_data.get("integral", 0)
                integral_alias = result_data.get("integralAlias", "积分")
                return {"integral": integral, "integralAlias": integral_alias}
        except RequestException as e:
            print(f"提交签到活动失败: {e}")
        return None

    def get_continuous_sign_days(self, mob: str, activity_id: str) -> Optional[int]:
        """获取连续签到天数"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/api/play-default/sign/current-month-signdays-new.do"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        current_time = datetime.now().strftime("%Y-%m")
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "activityId": activity_id,
            "time": current_time
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success"):
                result_data = data.get("data", {})
                continuous_sign_day = result_data.get("continuousSignDay", 0)
                return continuous_sign_day
        except RequestException as e:
            print(f"获取连续签到天数失败: {e}")
        return None

    def get_lottery_activity_id(self, mob: str):
        """获取抽奖活动ID和名称"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/common/common/get-customer-page"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "pageId": 111079,
            "pageType": 2
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success"):
                page_json_str = data.get("data", {}).get("result", {}).get("pageJson", "")
                if page_json_str:
                    try:
                        page_json_str = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', page_json_str)
                        page_json = json.loads(page_json_str)
                        for module in page_json.get('moduleList', []):
                            if module.get('type') == 'imgAd':
                                detail = module.get('detail', {})
                                link_list = detail.get('linkList', [])
                                for link in link_list:
                                    zones = link.get('zones', [])
                                    for zone in zones:
                                        link_url = zone.get('linkUrl', '')
                                        if '抽奖' in link_url:
                                            return zone.get('id'), link_url
                    except json.JSONDecodeError as e:
                        print(f"解析抽奖pageJson失败: {e}")
        except RequestException as e:
            print(f"获取抽奖活动ID失败: {e}")
        return None, None

    def get_lottery_times(self, mob: str, activity_id: str) -> int:
        """获取抽奖次数"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/activity/draw/child/receiveFreeTimes"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "activityId": activity_id
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success") and data.get("data", {}).get("success"):
                total_times = data["data"]["data"].get("totalTimes", 0)
                return total_times
        except RequestException as e:
            print(f"获取抽奖次数失败: {e}")
        return 0

    def lottery(self, mob: str, activity_id: str) -> bool:
        """提交抽奖"""
        timestamp = self.get_timestamp()
        url = f"{self.base_url}/jfmb/cloud/activity/draw/startTurntable"
        params = {
            "sideType": "3",
            "mob": mob,
            "appId": self.app_id,
            "shopNick": self.app_id,
            "timestamp": timestamp
        }
        payload = {
            "appId": self.app_id,
            "openId": True,
            "shopNick": "",
            "timestamp": timestamp,
            "interfaceSource": 0,
            "activityId": activity_id
        }
        try:
            response = self.session.post(
                url,
                params=params,
                json=payload,
                headers=self.headers
            )
            response.raise_for_status()
            data = response.json()
            if data.get("success") and data.get("data", {}).get("success"):
                prize_result = data["data"]["data"].get("prizeResult", {})
                prize_name = prize_result.get("prizeName", "未知奖品")
                print(f"抽奖成功，获得{prize_name}")
                return True
            else:
                print(f"抽奖失败: {data.get('errorMessage', '未知错误')}")
        except RequestException as e:
            print(f"抽奖请求失败: {e}")
        return False

    def process_account(self, nick_name: str, code: str) -> bool:
        """处理单个账号的签到和抽奖"""
        print(f"\n开始处理账号: {nick_name}")
        
        # 1. 登录
        if not self.login(code):
            print(f"账号 {nick_name} 登录失败")
            self.clear_session()
            return False
            
        # 2. 获取账号信息
        client_info = self.get_client_info()
        if not client_info:
            print(f"获取账号 {nick_name} 信息失败")
            return False
            
        client_name = client_info.get("client_name", nick_name)
        residual_integral = client_info.get("residualIntegral", 0)
        user_mob = client_info.get("user_mob", "")
        print(f"{client_name}登录成功，当前积分{residual_integral}")
        
        # 3. 处理签到活动
        print("\n--- 每日签到 ---")
        signin_id = self.get_signin_activity_id(user_mob)
        if signin_id:
            self.signin(user_mob, signin_id)
        else:
            print("获取签到活动ID失败")
            
        # 4. 处理其他签到活动
        print("\n--- 签到活动 ---")
        try:
            activity_id = self.get_activity_list(user_mob)
            if activity_id:
                prize_name = self.get_sign_prize(user_mob, activity_id)
                if prize_name:
                    print(f"签到奖励：{prize_name}")
                    
                sign_result = self.add_sign_new(user_mob, activity_id)
                if sign_result:
                    integral = sign_result.get("integral", 0)
                    integral_alias = sign_result.get("integralAlias", "积分")
                    print(f"签到成功，获得{integral}{integral_alias}")
                    
                    continuous_days = self.get_continuous_sign_days(user_mob, activity_id)
                    if continuous_days is not None:
                        print(f"已连续签到{continuous_days}天")
                else:
                    print("签到活动提交失败")
        except Exception as e:
            print(f"签到活动处理失败，跳过: {e}")
            
        # 5. 处理抽奖活动
        print("\n--- 抽奖活动 ---")
        lottery_id, lottery_name = self.get_lottery_activity_id(user_mob)
        if lottery_id:
            lottery_times = self.get_lottery_times(user_mob, lottery_id)
            print(f"抽奖活动\"{lottery_name}\"可免费抽奖{lottery_times}次")
            if lottery_times > 0:
                self.lottery(user_mob, lottery_id)
            else:
                print("没有免费抽奖次数")
        else:
            print("获取抽奖活动ID失败")
            
        self.clear_session()
        return True

def main():
    """主函数"""
    print("="*50)
    print("杰士邦会员中心签到脚本 - 完整修复版")
    print(f"当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
    print("="*50)
    
    # 检查依赖
    try:
        import requests
    except ImportError:
        print("❌ 错误: 需要安装 requests 库")
        print("请执行: pip install requests")
        return
    
    if not LOCAL_PROXY:
        print("❌ 错误: 未检测到API_PROXY环境变量")
        print("请设置环境变量: export API_PROXY='你的协议服务IP:端口'")
        print("示例: export API_PROXY='192.168.0.183:5000'")
        return
    
    try:
        signin = JieshibangSignin()
        
        # 1. 获取微信账号列表
        print("\n[1/4] 正在获取微信账户列表...")
        wx_accounts = signin.get_wx_list()
        if not wx_accounts:
            print("❌ 未获取到任何在线账号")
            print("可能原因:")
            print(f"1. 协议服务不可达 (当前配置: {LOCAL_PROXY})")
            print("2. 协议服务未正常运行")
            print("3. 网络连接问题")
            return
            
        print(f"✅ 获取到 {len(wx_accounts)} 个账号")
        
        # 2. 根据环境变量过滤账号
        allowed_wxids = [x.strip() for x in os.getenv("Applet_JSB_Wxid", "").split(",") if x.strip()]
        if allowed_wxids:
            print(f"允许的Wxid: {', '.join(allowed_wxids)}")
            filtered_accounts = [acc for acc in wx_accounts if acc.get("Wxid") in allowed_wxids]
            print(f"根据Applet_JSB_Wxid过滤后，需要处理 {len(filtered_accounts)} 个账号")
        else:
            filtered_accounts = wx_accounts
            print("未设置Applet_JSB_Wxid环境变量，处理所有账号")
        
        if not filtered_accounts:
            print("❌ 没有符合条件的账号需要处理")
            return
        
        # 3. 处理每个账号
        print("\n[2/4] 开始处理账号...")
        for i, account in enumerate(filtered_accounts, 1):
            wxid = account.get("Wxid")
            wxname = account.get("wxname", f"账号{i}")
            
            print(f"\n{'='*30}")
            print(f"处理账号 {i}/{len(filtered_accounts)}: {wxname} (Wxid: {wxid})")
            print(f"{'='*30}")
            
            code = signin.get_wx_code(wxid)
            if not code:
                print(f"⚠️ 跳过 {wxname} - 获取登录Code失败")
                continue
                
            try:
                if signin.process_account(wxname, code):
                    print(f"✅ {wxname} 处理成功")
                else:
                    print(f"⚠️ {wxname} 处理失败")
            except Exception as e:
                print(f"❌ {wxname} 处理异常: {e}")
                
            if i < len(filtered_accounts):
                wait_time = random.randint(2, 5)
                print(f"等待{wait_time}秒后继续...")
                time.sleep(wait_time)
        
        print("\n[3/4] 所有账号处理完成")
        print(f"\n执行完成 @ {time.strftime('%Y-%m-%d %H:%M:%S')}")
        
    except Exception as e:
        print(f"\n❌ 脚本执行失败: {e}")
    finally:
        print("\n" + "="*50)

if __name__ == "__main__":
    main()